home *** CD-ROM | disk | FTP | other *** search
Text File | 1997-01-17 | 54.8 KB | 1,251 lines | [TEXT/R*ch] |
- Archive-name: C++-faq/part5
- Posting-Frequency: monthly
- Last-modified: Jan 1, 1997
- URL: http://www.cerfnet.com/~mpcline/c++-faq-lite/
-
- AUTHOR: Marshall Cline / cline@parashift.com / Paradigm Shift, Inc. /
- One Park St. / Norwood, NY 13668 / 315-353-6100 (voice) / 315-353-6110 (fax)
-
- COPYRIGHT: This posting is part of "C++ FAQ Lite." The entire "C++ FAQ Lite"
- document is Copyright(C) 1991-96 Marshall P. Cline, Ph.D., cline@parashift.com.
- All rights reserved. Copying is permitted only under designated situations.
- For details, see section [1].
-
- NO WARRANTY: THIS WORK IS PROVIDED ON AN "AS IS" BASIS. THE AUTHOR PROVIDES NO
- WARRANTY WHATSOEVER, EITHER EXPRESS OR IMPLIED, REGARDING THE WORK, INCLUDING
- WARRANTIES WITH RESPECT TO ITS MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR
- PURPOSE.
-
- C++-FAQ-Lite != C++-FAQ-Book: This document, C++ FAQ Lite, is not the same as
- the C++ FAQ Book. The book (C++ FAQs, Cline and Lomow, Addison-Wesley) is 500%
- larger than this document, and is available in bookstores. For details, see
- section [3].
-
- ==============================================================================
-
- SECTION [17]: Exceptions and error handling
-
-
- [17.1] How can I handle a constructor that fails?
-
- Throw an exception.
-
- Constructors don't have a return type, so it's not possible to use error codes.
- The best way to signal constructor failure is therefore to throw an exception.
-
- If you don't have or won't use exceptions, here's a work-around. If a
- constructor fails, the constructor can put the object into a "zombie" state.
- Do this by setting an internal status bit so the object acts sort of like its
- dead even though it is technically still alive. Then add a query ("inspector")
- member function to check this "zombie" bit so users of your class can find out
- if their object is truly alive, or if it's a zombie (i.e., a "living dead"
- object). Also you'll probably want to have your other member functions check
- this zombie bit, and, if the object isn't really alive, do a no-op (or perhaps
- something more obnoxious such as abort()). This is really ugly, but it's the
- best you can do if you can't (or don't want to) use exceptions.
-
- ==============================================================================
-
- [17.2] How should I handle resources if my constructors may throw exceptions?
-
- Every data member inside your object should clean up its own mess.
-
- If a constructor throws an exception, the object's destructor is not run. If
- your object has already done something that needs to be undone (such as
- allocating some memory, opening a file, or locking a semaphore), this "stuff
- that needs to be undone" must be remembered by a data member inside the object.
-
- For example, rather than allocating memory into a raw Fred* data member, put
- the allocated memory into a "smart pointer" member object, and the destructor
- of this smart pointer will delete the Fred object when the smart pointer dies.
- The standard class auto_ptr is an example of such as "smart pointer" class.
- You can also write your own reference counting smart pointer[16.19]. You can
- also use smart pointers to "point" to disk records or objects on other
- machines[13.3].
-
- ==============================================================================
-
- [17.3] How do I change the string-length of an array of char to prevent memory
- leaks even if/when someone throws an exception?
-
- If what you really want to do is work with strings, don't use an array of char
- in the first place, since arrays are evil[21.5]. Instead use an object of some
- string-like class.
-
- For example, suppose you want to get a copy of a string, fiddle with the copy,
- then append another string to the end of the fiddled copy. The array-of-char
- approach would look something like this:
-
- void userCode(const char* s1, const char* s2)
- {
- // Get a copy of s1 into a new string called copy:
- char* copy = new char[strlen(s1) + 1];
- strcpy(copy, s1);
-
- // Now that we have a local pointer to freestore-allocated memory,
- // we need to use a try block to prevent memory leaks:
- try {
-
- // Now we fiddle with copy for a while...
- // ...
-
- // Later we want to append s2 onto the fiddled-with copy:
- // ... [Here's where people want to reallocate copy] ...
- char* copy2 = new char[strlen(copy) + strlen(s2) + 1];
- strcpy(copy2, copy);
- strcpy(copy2 + strlen(copy), s2);
- delete[] copy;
- copy = copy2;
-
- // Finally we fiddle with copy again...
- // ...
-
- } catch (...) {
- delete[] copy; // Prevent memory leaks if we got an exception
- throw; // Re-throw the current exception
- }
-
- delete[] copy; // Prevent memory leaks if we did NOT get an exception
- }
-
- Using char*s like this is tedious and error prone. Why not just use an object
- of some string class? Your compiler probably supplies a string-like class, and
- it's probably just as fast and certainly it's a lot simpler and safer than the
- char* code that you would have to write yourself. For example, if you're using
- the string class from the standardization committee[6.12], your code might look
- something like this:
-
- #include <string> // Let the compiler see class string
- using namespace std;
-
- void userCode(const string& s1, const string& s2)
- {
- // Get a copy of s1 into a new string called copy:
- string copy = s1; // NOTE: we do NOT need a try block!
-
- // Now we fiddle with copy for a while...
- // ...
-
- // Later we want to append s2 onto the fiddled-with copy:
- copy += s2; // NOTE: we do NOT need to reallocate memory!
-
- // Finally we fiddle with copy again...
- // ...
- } // NOTE: we do NOT need to delete[] anything!
-
- ==============================================================================
-
- SECTION [18]: Const correctness
-
-
- [18.1] What is "const correctness"? [UPDATED!]
-
- [Recently rewrote because of helpful feedback from Nor Jaidi (on 1/97).]
-
- A good thing. It means using the keyword const to prevent const objects from
- getting mutated.
-
- For example, if you wanted to create a function f() that accepted a String,
- plus you want to promise callers not to change the caller's String that gets
- passed to f(), you can have f() receive its String parameter . . .
- * void f1(const String& s); // Pass by reference-to-const
- * void f2(const String* sptr); // Pass by pointer-to-const
- * void f3(String s); // Pass by value
-
- In the pass by reference-to-const and pass by pointer-to-const cases, any
- attempts to change to the caller's String within the f() functions would be
- flagged by the compiler as an error at compile-time. This check is done
- entirely at compile-time: there is no run-time space or speed cost for the
- const. In the pass by value case (f3()), the called function gets a copy of
- the caller's String. This means that f3() can change its local copy, but the
- copy is destroyed when f3() returns. In particular f3() cannot change the
- caller's String object.
-
- As an opposite example, if you wanted to create a function g() that accepted a
- String, but you want to let callers know that g() might change the caller's
- String object. In this case you can have g() receive its String parameter . .
- .
- * void g1(String& s); // Pass by reference-to-non-const
- * void g2(String* sptr); // Pass by pointer-to-non-const
-
- The lack of const in these functions tells the compiler that they are allowed
- to (but are not required to) change the caller's String object. Thus they can
- pass their String to any of the f() functions, but only f3() (the one that
- receives its parameter "by value") can pass its String to g1() or g2(). If
- f1() or f2() need to call either g() function, a local copy of the String
- object must be passed to the g() function; the parameter to f1() or f2() cannot
- be directly passed to either g() function. E.g.,
-
- void g1(String& s);
-
- void f1(const String& s)
- {
- g1(s); // Compile-time Error since s is const
-
- String localCopy = s;
- g1(localCopy); // OK since localCopy is not const
- }
-
- Naturally in the above case, any changes that g1() makes are made to the
- localCopy object that is local to f1(). In particular, no changes will be made
- to the const parameter that was passed by reference to f1().
-
- ==============================================================================
-
- [18.2] How is "const correctness" related to ordinary type safety?
-
- Declaring the const-ness of a parameter is just another form of type safety.
- It is almost as if a const String, for example, is a different class than an
- ordinary String, since the const variant is missing the various mutative
- operations in the non-const variant (e.g., you can imagine that a const String
- simply doesn't have an assignment operator).
-
- If you find ordinary type safety helps you get systems correct (it does;
- especially in large systems), you'll find const correctness helps also.
-
- ==============================================================================
-
- [18.3] Should I try to get things const correct "sooner" or "later"?
-
- At the very, very, very beginning.
-
- Back-patching const correctness results in a snowball effect: every const you
- add "over here" requires four more to be added "over there."
-
- ==============================================================================
-
- [18.4] What does "const Fred* p" mean?
-
- It means p points to an object of class Fred, but p can't be used to change
- that Fred object (naturally p could also be NULL).
-
- For example, if class Fred has a const member function[18.9] called inspect(),
- saying p->inspect() is OK. But if class Fred has a non-const member
- function[18.9] called mutate(), saying p->mutate() is an error (the error is
- caught by the compiler; no run-time tests are done, which means const doesn't
- slow your program down).
-
- ==============================================================================
-
- [18.5] What's the difference between "const Fred* p", "Fred* const p" and
- "const Fred* const p"?
-
- You have to read pointer declarations right-to-left.
- * const Fred* p means "p points to a Fred that is const" -- that is, the Fred
- object can't be changed via p[18.13].
- * Fred* const p means "p is a const pointer to a Fred" -- that is, you can
- change the Fred object via p[18.13], but you can't change the pointer p
- itself.
- * const Fred* const p means "p is a const pointer to a const Fred" -- that is,
- you can't change the pointer p itself, nor can you change the Fred object
- via p[18.13].
-
- ==============================================================================
-
- [18.6] What does "const Fred& x" mean?
-
- It means x aliases a Fred object, but x can't be used to change that Fred
- object.
-
- For example, if class Fred has a const member function[18.9] called inspect(),
- saying x.inspect() is OK. But if class Fred has a non-const member
- function[18.9] called mutate(), saying x.mutate() is an error (the error is
- caught by the compiler; no run-time tests are done, which means const doesn't
- slow your program down).
-
- ==============================================================================
-
- [18.7] Does "Fred& const x" make any sense?
-
- No, it is nonsense.
-
- To find out what the above declaration means, you have to read it
- right-to-left[18.5]. Thus "Fred& const x" means "x is a const reference to a
- Fred". But that is redundant, since references are always const. You can't
- reseat a reference[8.4]. Never. With or without the const.
-
- In other words, "Fred& const x" is functionally equivalent to "Fred& x". Since
- you're gaining nothing by adding the const after the &, you shouldn't add it
- since it will confuse people. I.e., the const will make some people think that
- the Fred is const, as if you had said "const Fred& x".
-
- ==============================================================================
-
- [18.8] What does "Fred const& x" mean?
-
- "Fred const& x" is functionally equivalent to "const Fred& x"[18.6].
-
- The problem with using "Fred const& x" (with the const before the &) is that it
- could easily be mis-typed as the nonsensical "Fred &const x"[18.7] (with the
- const after the &).
-
- Better to simply use const Fred& x.
-
- ==============================================================================
-
- [18.9] What is a "const member function"?
-
- A member function that inspects (rather than mutates) its object.
-
- A const member function is indicated by a const suffix just after the member
- function's parameter list. Member functions with a const suffix are called
- "const member functions" or "inspectors." Member functions without a const
- suffix are called "non-const member functions" or "mutators."
-
- class Fred {
- public:
- void inspect() const; // This member promises NOT to change *this
- void mutate(); // This member function might change *this
- };
-
- void userCode(Fred& changeable, const Fred& unchangeable)
- {
- changeable.inspect(); // OK: doesn't change a changeable object
- changeable.mutate(); // OK: changes a changeable object
-
- unchangeable.inspect(); // OK: doesn't change an unchangeable object
- unchangeable.mutate(); // ERROR: attempt to change unchangeable object
- }
-
- The error in unchangeable.mutate() is caught at compile time. There is no
- runtime space or speed penalty for const.
-
- The trailing const on inspect() member function means that the abstract
- (client-visible) state of the object isn't going to change. This is slightly
- different from promising that the "raw bits" of the object's struct aren't
- going to change. C++ compilers aren't allowed to take the "bitwise"
- interpretation unless they can solve the aliasing problem, which normally can't
- be solved (i.e., a non-const alias could exist which could modify the state of
- the object). Another (important) insight from this aliasing issue: pointing at
- an object with a const pointer doesn't guarantee that the object won't change;
- it promises only that the object won't change via that pointer).
-
- ==============================================================================
-
- [18.10] What do I do if I want to update an "invisible" data member inside a
- const member function?
-
- Use mutable, or use const_cast.
-
- A small percentage of inspectors need to make innocuous changes to data members
- (e.g., a Set object might want to cache its last lookup in hopes of improving
- the performance of its next lookup). By saying the changes are "innocuous," I
- mean that the changes wouldn't be visible from outside the object's interface
- (otherwise the member function would be a mutator rather than an inspector).
-
- When this happens, the data member which will be modified should be marked as
- mutable (put the mutable keyword just before the data member's declaration;
- i.e., in the same place where you could put const). This tells the compiler
- that the data member is allowed to change during a const member function. If
- your compiler doesn't support the mutable keyword, you can cast away the
- const'ness of this via the const_cast keyword. E.g., in Set::lookup() const,
- you might say,
-
- Set* self = const_cast<Set*>(this);
-
- After this line, self will have the same bits as this (e.g., self == this), but
- self is a Set* rather than a const Set*. Therefore you can use self to modify
- the object pointed to by this.
-
- ==============================================================================
-
- [18.11] Does const_cast mean lost optimization opportunities?
-
- In theory, yes; in practice, no.
-
- Even if the language outlawed const_cast, the only way to avoid flushing the
- register cache across a const member function call would be to solve the
- aliasing problem (i.e., to prove that there are no non-const pointers that
- point to the object). This can happen only in rare cases (when the object is
- constructed in the scope of the const member function invocation, and when all
- the non-const member function invocations between the object's construction and
- the const member function invocation are statically bound, and when every one
- of these invocations is also inlined, and when the constructor itself is
- inlined, and when any member functions the constructor calls are inline).
-
- ==============================================================================
-
- [18.12] Why does the compiler allow me to change an int after I've pointed at
- it with a const int*?
-
- Because "const int* p" means "p promises not to change the *p," not "*p
- promises not to change."
-
- Causing a const int* to point to an int doesn't const-ify the int. The int
- can't be changed via the const int*, but if someone else has a int* (note: no
- const) that points to ("aliases") the same int, then that int* can be used to
- change the int. For example:
-
- void f(const int* p1, int* p2)
- {
- int i = *p1; // Get the (original) value of *p1
- *p2 = 7; // If p1 == p2, this will also change *p1
- int j = *p1; // Get the (possibly new) value of *p1
- if (i != j) {
- cout << "*p1 changed, but it didn't change via pointer p1!\n";
- assert(p1 == p2); // This is the only way *p1 could be different
- }
- }
-
- main()
- {
- int x;
- f(&x, &x); // This is perfectly legal (and even moral!)
- }
-
- Note that main() and f(const int*,int*) could be in different compilation units
- that are compiled on different days of the week. In that case there is no way
- the compiler can possibly detect the aliasing at compile time. Therefore there
- is no way we could make a language rule that prohibits this sort of thing. In
- fact, we wouldn't even want to make such a rule, since in general it's
- considered a feature that you can have many pointers pointing to the same
- thing. The fact that one of those pointers promises not to change the
- underlying "thing" is just a promise made by the pointer; it's not a promise
- made by the "thing".
-
- ==============================================================================
-
- [18.13] Does "const Fred* p" mean that *p can't change?
-
- No! (This is related to the FAQ about aliasing of int pointers[18.12].)
-
- "const Fred* p" means that the Fred can't be changed via pointer p, but any
- aliasing pointers that aren't const can be used to change the Fred object. For
- example, if you have two pointers "const Fred* p" and "Fred* q" that point to
- the same Fred object (aliasing), pointer q can be used to change the Fred
- object but pointer p cannot.
-
- class Fred {
- public:
- void inspect() const; // A const member function[18.9]
- void mutate(); // A non-const member function[18.9]
- };
-
- main()
- {
- Fred f;
- const Fred* p = &f;
- Fred* q = &f;
-
- p->insepct(); // OK: No change to *p
- p->mutate(); // Error: Can't change *p via p
-
- q->inspect(); // OK: q is allowed to inspect the object
- q->mutate(); // OK: q is allowed to mutate the object
- }
-
- ==============================================================================
-
- SECTION [19]: Inheritance -- basics
-
-
- [19.1] Is inheritance important to C++?
-
- Yep.
-
- Inheritance is what separates abstract data type (ADT) programming from OO
- programming.
-
- ==============================================================================
-
- [19.2] When would I use inheritance?
-
- As a specification device.
-
- Human beings abstract things on two dimensions: part-of and kind-of. A Ford
- Taurus is-a-kind-of-a Car, and a Ford Taurus has-a Engine, Tires, etc. The
- part-of hierarchy has been a part of software since the ADT style became
- relevant; inheritance adds "the other" major dimension of decomposition.
-
- ==============================================================================
-
- [19.3] How do you express inheritance in C++?
-
- By the : public syntax:
-
- class Car : public Vehicle {
- public:
- // ...
- };
-
- We state the above relationship in several ways:
- * Car is "a kind of a" Vehicle
- * Car is "derived from" Vehicle
- * Car is "a specialized" Vehicle
- * Car is the "subclass" of Vehicle
- * Vehicle is the "base class" of Car
- * Vehicle is the "superclass" of Car (this not as common in the C++ community)
-
- (Note: this FAQ has to do with public inheritance; private and protected
- inheritance[24] are different.)
-
- ==============================================================================
-
- [19.4] Is it OK to convert a pointer from a derived class to its base class?
-
- Yes.
-
- An object of a derived class is a kind of the base class. Therefore the
- conversion from a derived class pointer to a base class pointer is perfectly
- safe, and happens all the time. For example, if I am pointing at a car, I am
- in fact pointing at a vehicle, so converting a Car* to a Vehicle* is perfectly
- safe and normal:
-
- void f(Vehicle* v);
- void g(Car* c) { f(c); } // Perfectly safe; no cast
-
- (Note: this FAQ has to do with public inheritance; private and protected
- inheritance[24] are different.)
-
- ==============================================================================
-
- [19.5] What's the difference between public:, private:, and protected:?
-
- * A member (either data member or member function) declared in a private:
- section of a class can only be accessed by member functions and friends[14]
- of that class
- * A member (either data member or member function) declared in a protected:
- section of a class can only be accessed by member functions and friends[14]
- of that class, and by member functions and friends[14] of derived classes
- * A member (either data member or member function) declared in a public:
- section of a class can be accessed by anyone
-
- ==============================================================================
-
- [19.6] Why can't my derived class access private: things from my base class?
-
- To protect you from future changes to the base class.
-
- Derived classes do not get access to private members of a base class. This
- effectively "seals off" the derived class from any changes made to the private
- members of the base class.
-
- ==============================================================================
-
- [19.7] How can I protect subclasses from breaking when I change internal parts?
-
- A class has two distinct interfaces for two distinct sets of clients:
- * It has a public: interface that serves unrelated classes
- * It has a protected: interface that serves derived classes
-
- Unless you expect all your subclasses to be built by your own team, you should
- consider making your base class's bits be private:, and use protected: inline
- access functions by which derived classes will access the private data in the
- base class. This way the private bits can change, but the derived class's code
- won't break unless you change the protected access functions.
-
- ==============================================================================
-
- SECTION [20]: Inheritance -- virtual functions
-
-
- [20.1] What is a "virtual member function"?
-
- From an OO perspective, it is the single most important feature of C++: [6.8],
- [6.9].
-
- A virtual function allows derived classes to replace the implementation
- provided by the base class. The compiler makes sure the replacement is always
- called whenever the object in question is actually of the derived class, even
- if the object is accessed by a base pointer rather than a derived pointer.
- This allows algorithms in the base class to be replaced in the derived class,
- even if users don't know about the derived class.
-
- The derived class can either fully replace ("override") the base class member
- function, or the derived class can partially replace ("augment") the base class
- member function. The latter is accomplished by having the derived class member
- function call the base class member function, if desired.
-
- ==============================================================================
-
- [20.2] How can C++ achieve dynamic binding yet also static typing?
-
- When you have a pointer to an object, the object may actually be of a class
- that is derived from the class of the pointer (e.g., a Vehicle* that is
- actually pointing to a Car object). Thus there are two types: the (static)
- type of the pointer (Vehicle, in this case), and the (dynamic) type of the
- pointed-to object (Car, in this case).
-
- Static typing means that the legality of a member function invocation is
- checked at the earliest possible moment: by the compiler at compile time. The
- compiler uses the static type of the pointer to determine whether the member
- function invocation is legal. If the type of the pointer can handle the member
- function, certainly the pointed-to object can handle it as well. E.g., if
- Vehicle has a certain member function, certainly Car also has that member
- function since Car is a kind-of Vehicle.
-
- Dynamic binding means that the address of the code in a member function
- invocation is determined at the last possible moment: based on the dynamic type
- of the object at run time. It is called "dynamic binding" because the binding
- to the code that actually gets called is accomplished dynamically (at run
- time). Dynamic binding is a result of virtual functions.
-
- ==============================================================================
-
- [20.3] What's the difference between how virtual and non-virtual member
- functions are called? [NEW!]
-
- [Recently created (on 11/96).]
-
- Non-virtual member functions are resolved statically. That is, the member
- function is selected statically (at compile-time) based on the type of the
- pointer (or reference) to the object.
-
- In contrast, virtual member functions are resolved dynamically (at run-time).
- That is, the member function is selected dynamically (at run-time) based on the
- type of the object, not the type of the pointer/reference to that object. This
- is called "dynamic binding." Most compilers use some variant of the following
- technique: if the object has one or more virtual functions, the compiler puts a
- hidden pointer in the object called a "virtual-pointer" or "v-pointer." This
- v-pointer points to a global table called the "virtual-table" or "v-table."
-
- The compiler creates a v-table for each class that has at least one virtual
- function. For example, if class Circle has virtual functions for draw() and
- move() and resize(), there would be exactly one v-table associated with class
- Circle, even if there were a gazillion Circle objects, and the v-pointer of
- each of those Circle objects would point to the Circle v-table. The v-table
- itself has pointers to each of the virtual functions in the class. For
- example, the Circle v-table would have three pointers: a pointer to
- Circle::draw(), a pointer to Circle::move(), and a pointer to Circle::resize().
-
- During a dispatch of a virtual function, the run-time system follows the
- object's v-pointer to the class's v-table, then follows the appropriate slot in
- the v-table to the method code.
-
- The space-cost overhead of the above technique is nominal: an extra pointer per
- object (but only for objects that will need to do dynamic binding), plus an
- extra pointer per method (but only for virtual methods). The time-cost
- overhead is also fairly nominal: compared to a normal function call, a virtual
- function call requires two extra fetches (one to get the value of the
- v-pointer, a second to get the address of the method). None of this runtime
- activity happens with non-virtual functions, since the compiler resolves
- non-virtual functions exclusively at compile-time based on the type of the
- pointer.
-
- Note: the above discussion is simplified considerably, since it doesn't account
- for extra structural things like multiple inheritance, virtual inheritance,
- RTTI, etc., nor does it account for space/speed issues such as page faults,
- calling a function via a pointer-to-function, etc. If you want to know about
- those other things, please ask comp.lang.c++; PLEASE DO NOT SEND E-MAIL TO ME!
-
- ==============================================================================
-
- [20.4] When should my destructor be virtual?
-
- When you may delete a derived object via a base pointer.
-
- virtual functions bind to the code associated with the class of the object,
- rather than with the class of the pointer/reference. When you say
- delete basePtr, and the base class has a virtual destructor, the destructor
- that gets invoked is the one associated with the type of the object *basePtr,
- rather than the one associated with the type of the pointer. This is generally
- A Good Thing.
-
- TECHNO-GEEK WARNING; PUT YOUR PROPELLER HAT ON.
- Technically speaking, you need a base class's destructor to be virtual if and
- only if you intend to allow someone to invoke an object's destructor via a base
- class pointer (this is normally done implicitly via delete), and the object
- being destructed is of a derived class that has a non-trivial destructor. A
- class has a non-trivial destructor if it either has an explicit destructor, or
- if it has a member object or a base class that has a non-trivial destructor
- (note that this is a recursive definition (e.g., a class has a non-trivial
- destructor if it has a member object (which has a base class (which has a
- member object (which has a base class (which has an explicit destructor)))))).
- END TECHNO-GEEK WARNING; REMOVE YOUR PROPELLER HAT
-
- If you had a hard grokking the previous rule, try this (over)simplified one on
- for size: A class should have a virtual destructor unless that class has no
- virtual functions. Rationale: if you have any virtual functions at all, you're
- probably going to be doing "stuff" to derived objects via a base pointer, and
- some of the "stuff" you may do may include invoking a destructor (normally done
- implicitly via delete). Plus once you've put the first virtual function into a
- class, you've already paid all the per-object space cost that you'll ever pay
- (one pointer per object; note that this is theoretically compiler-specific; in
- practice everyone does it pretty much the same way), so making the destructor
- virtual won't generally cost you anything extra.
-
- ==============================================================================
-
- [20.5] What is a "virtual constructor"?
-
- An idiom that allows you to do something that C++ doesn't directly support.
-
- You can get the effect of a virtual constructor by a virtual clone() member
- function (for copy constructing), or a virtual create() member function (for
- the default constructor[10.4]).
-
- class Shape {
- public:
- virtual ~Shape() { } // A virtual destructor[20.4]
- virtual void draw() = 0; // A pure virtual function[22.4]
- virtual void move() = 0;
- // ...
- virtual Shape* clone() const = 0; // Uses the copy constructor
- virtual Shape* create() const = 0; // Uses the default constructor[10.4]
- };
-
- class Circle : public Shape {
- public:
- Circle* clone() const { return new Circle(*this); }
- Circle* create() const { return new Circle(); }
- // ...
- };
-
- In the clone() member function, the new Circle(*this) code calls Circle's copy
- constructor to copy the state of this into the newly created Circle object. In
- the create() member function, the new Circle() code calls Circle's default
- constructor[10.4].
-
- Users use these as if they were "virtual constructors":
-
- void userCode(Shape& s)
- {
- Shape* s2 = s.clone();
- Shape* s3 = s.create();
- // ...
- delete s2; // You probably need a virtual destructor[20.4] here
- delete s3;
- }
-
- This function will work correctly regardless of whether the Shape is a Circle,
- Square, or some other kind-of Shape that doesn't even exist yet.
-
- ==============================================================================
-
- SECTION [21]: Inheritance -- proper inheritance and substitutability
-
-
- [21.1] Should I hide member functions that were public in my base class?
-
- Never, never, never do this. Never. Never!
-
- Attempting to hide (eliminate, revoke, privatize) inherited public member
- functions is an all-too-common design error. It usually stems from muddy
- thinking.
-
- (Note: this FAQ has to do with public inheritance; private and protected
- inheritance[24] are different.)
-
- ==============================================================================
-
- [21.2] Derived* --> Base* works OK; why doesn't Derived** --> Base** work?
-
- C++ allows a Derived* to be converted to a Base*, since a Derived object is a
- kind of a Base object. However trying to convert a Derived** to a Base** is
- flagged as an error. Although this error may not be obvious, it is nonetheless
- a good thing. For example, if you could convert a Car** to a Vehicle**, and if
- you could similarly convert a NuclearSubmarine** to a Vehicle**, you could
- assign those two pointers and end up making a Car* point at a NuclearSubmarine:
-
- class Vehicle { /*...*/ };
- class Car : public Vehicle { /*...*/ };
- class NuclearSubmarine : public Vehicle { /*...*/ };
-
- main()
- {
- Car car;
- Car* carPtr = &car;
- Car** carPtrPtr = &carPtr;
- Vehicle** vehiclePtrPtr = carPtrPtr; // This is an error in C++
- NuclearSubmarine sub;
- NuclearSubmarine* subPtr = ⊂
- *vehiclePtrPtr = subPtr;
- // This last line would have caused carPtr to point to sub !
- }
-
- In other words, if it was legal to convert a Derived** to a Base**, the Base**
- could be dereferenced (yielding a Base*), and the Base* could be made to point
- to an object of a different derived class, which could cause serious problems
- for national security (who knows what would happen if you invoked the
- openGasCap() member function on what you thought was a Car, but in reality it
- was a NuclearSubmarine!!)..
-
- (Note: this FAQ has to do with public inheritance; private and protected
- inheritance[24] are different.)
-
- ==============================================================================
-
- [21.3] Is a parking-lot-of-Car a kind-of parking-lot-of-Vehicle?
-
- Nope.
-
- I know it sounds strange, but it's true. You can think of this as a direct
- consequence of the previous FAQ, or you can reason it this way: if the kind-of
- relationship were valid, then someone could point a parking-lot-of-Vehicle
- pointer at a parking-lot-of-Car. But parking-lot-of-Vehicle has a
- addNewVehicleToParkingLot(Vehicle&) member function which can add any Vehicle
- object to the parking lot. This would allow you to park a NuclearSubmarine in
- a parking-lot-of-Car. Certainly it would be surprising if someone removed what
- they thought was a Car from the parking-lot-of-Car, only to find that it is
- actually a NuclearSubmarine.
-
- Another way to say this truth: a container of Thing is not a kind-of container
- of Anything even if a Thing is a kind-of an Anything. Swallow hard; it's true.
-
- You don't have to like it. But you do have to accept it.
-
- One last example which we use in our OO/C++ training courses: "A Bag-of-Apple
- is not a kind-of Bag-of-Fruit." If a Bag-of-Apple could be passed as a
- Bag-of-Fruit, someone could put a Banana into the Bag, even though it is
- supposed to only contain Apples!
-
- (Note: this FAQ has to do with public inheritance; private and protected
- inheritance[24] are different.)
-
- ==============================================================================
-
- [21.4] Is an array of Derived a kind-of array of Base?
-
- Nope.
-
- This is a corollary of the previous FAQ. Unfortunately this one can get you
- into a lot of hot water. Consider this:
-
- class Base {
- public:
- virtual void f(); // 1
- };
-
- class Derived : public Base {
- public:
- // ...
- private:
- int i_; // 2
- };
-
- void userCode(Base* arrayOfBase)
- {
- arrayOfBase[1].f(); // 3
- }
-
- main()
- {
- Derived arrayOfDerived[10]; // 4
- userCode(arrayOfDerived); // 5
- }
-
- The compiler thinks this is perfectly type-safe. Line 5 converts a Derived* to
- a Base*. But in reality it is horrendously evil: since Derived is larger than
- Base, the pointer arithmetic done on line 3 is incorrect: the compiler uses
- sizeof(Base) when computing the address for arrayOfBase[1], yet the array is an
- array of Derived, which means the address computed on line 3 (and the
- subsequent invocation of member function f()) isn't even at the beginning of
- any object! It's smack in the middle of a Derived object. Assuming your
- compiler uses the usual approach to virtual[20] functions, this will
- reinterpret the int i_ of the first Derived as if it pointed to a virtual
- table, it will follow that "pointer" (which at this point means we're digging
- stuff out of a random memory location), and grab one of the first few words of
- memory at that location and interpret them as if they were the address of a C++
- member function, then load that (random memory location) into the instruction
- pointer and begin grabbing machine instructions from that memory location. The
- chances of this crashing are very high.
-
- The root problem is that C++ can't distinguish between a pointer-to-a-thing and
- a pointer-to-an-array-of-things. Naturally C++ "inherited" this feature from
- C.
-
- NOTE: If we had used an array-like class (e.g., vector<Derived> from STL[32.1])
- instead of using a raw array, this problem would have been properly trapped as
- an error at compile time rather than a run-time disaster.
-
- (Note: this FAQ has to do with public inheritance; private and protected
- inheritance[24] are different.)
-
- ==============================================================================
-
- [21.5] Does array-of-Derived is-not-a-kind-of array-of-Base mean arrays are
- bad?
-
- Yes, arrays are evil. (only half kidding).
-
- Seriously, arrays are very closely related to pointers, and pointers are
- notoriously difficult to deal with. But if you have a complete grasp of why
- the above few FAQs were a problem from a design perspective (e.g., if you
- really know why a container of Thing is not a kind-of container of Anything),
- and if you think everyone else who will be maintaining your code also has a
- full grasp on these OO design truths, then you should feel free to use arrays.
- But if you're like most people, you should use a template container class such
- as vector<T> from STL[32.1] rather than raw arrays.
-
- (Note: this FAQ has to do with public inheritance; private and protected
- inheritance[24] are different.)
-
- ==============================================================================
-
- [21.6] Is a Circle a kind-of an Ellipse? [UPDATED!]
-
- [Recently added a caveat that setSize(x,y) isn't sacred (on 1/97).]
-
- Not if Ellipse promises to be able to change its size asymmetrically.
-
- For example, suppose Ellipse has a setSize(x,y) member function, and suppose
- this member function promises the Ellipse's width() will be x, and its height()
- will be y. In this case, Circle can't be a kind-of Ellipse. Simply put, if
- Ellipse can do something Circle can't, then Circle can't be a kind of Ellipse.
-
- This leaves two potential (valid) relationships between Circle and Ellipse:
- * Make Circle and Ellipse completely unrelated classes
- * Derive Circle and Ellipse from a base class representing "Ellipses that
- can't necessarily perform an unequal-setSize() operation"
-
- In the first case, Ellipse could be derived from class AsymmetricShape, and
- setSize(x,y) could be introduced in AsymmetricShape. However Circle could be
- derived from SymmetricShape which has a setSize(size) member function.
-
- In the second case, class Oval could only have setSize(size) which sets both
- the width() and the height() to size. Ellipse and Circle could both inherit
- from Oval. Ellipse --but not Circle-- could add the setSize(x,y) operation
- (but beware of the hiding rule[23.3] if the same member function name setSize()
- is used for both operations).
-
- (Note: this FAQ has to do with public inheritance; private and protected
- inheritance[24] are different.)
-
- (Note: setSize(x,y) isn't sacred. Depending on your goals, it may be okay to
- prevent users from changing the dimensions of an Ellipse, in which case it
- would be a valid design choice to not have a setSize(x,y) method in Ellipse.
- However this series of FAQs discusses what to do when you want to create a
- derived class of a pre-existing base class that has an "unacceptable" method in
- it. Of course the ideal situation is to discover this problem when the base
- class doesn't yet exist. But life isn't always ideal...)
-
- ==============================================================================
-
- [21.7] Are there other options to the "Circle is/isnot kind-of Ellipse"
- dilemma? [UPDATED!]
-
- [Recently added a caveat that setSize(x,y) isn't sacred (on 1/97).]
-
- If you claim that all Ellipses can be squashed asymmetrically, and you claim
- that Circle is a kind-of Ellipse, and you claim that Circle can't be squashed
- asymmetrically, clearly you've got to adjust (revoke, actually) one of your
- claims. Thus you've either got to get rid of Ellipse::setSize(x,y), get rid of
- the inheritance relationship between Circle and Ellipse, or admit that your
- Circles aren't necessarily circular.
-
- Here are the two most common traps new OO/C++ programmers regularly fall into.
- They attempt to use coding hacks to cover up a broken design (they redefine
- Circle::setSize(x,y) to throw an exception, call abort(), choose the average of
- the two parameters, or to be a no-op). Unfortunately all these hacks will
- surprise users, since users are expecting width() == x and height() == y. The
- one thing you must not do is surprise your users.
-
- If it is important to you to retain the "Circle is a kind-of Ellipse"
- inheritance relationship, you can weaken the promise made by Ellipse's
- setSize(x,y). E.g., you could change the promise to, "This member function
- might set width() to x and/or it might set height() to y, or it might do
- nothing". Unfortunately this dilutes the contract into dribble, since the user
- can't rely on any meaningful behavior. The whole hierarchy therefore begins to
- be worthless (it's hard to convince someone to use an object if you have to
- shrug your shoulders when asked what the object does for them).
-
- (Note: this FAQ has to do with public inheritance; private and protected
- inheritance[24] are different.)
-
- (Note: setSize(x,y) isn't sacred. Depending on your goals, it may be okay to
- prevent users from changing the dimensions of an Ellipse, in which case it
- would be a valid design choice to not have a setSize(x,y) method in Ellipse.
- However this series of FAQs discusses what to do when you want to create a
- derived class of a pre-existing base class that has an "unacceptable" method in
- it. Of course the ideal situation is to discover this problem when the base
- class doesn't yet exist. But life isn't always ideal...)
-
- ==============================================================================
-
- [21.8] But I have a Ph.D. in Mathematics, and I'm sure a Circle is a kind of an
- Ellipse! Does this mean Marshall Cline is stupid? Or that C++ is stupid?
- Or that OO is stupid? [UPDATED!]
-
- [Recently added a caveat that setSize(x,y) isn't sacred (on 1/97).]
-
- Actually, it doesn't mean any of these things. The sad reality is that it
- means your intuition is wrong.
-
- Look, I have received and answered dozens of passionate e-mail messages about
- this subject. I have taught it hundreds of times to thousands of software
- professionals all over the place. I know it goes against your intuition. But
- trust me; your intuition is wrong.
-
- The real problem is your intuitive notion of "kind of" doesn't match the OO
- notion of proper inheritance (technically called "subtyping"). The bottom line
- is that the derived class objects must be substitutable for the base class
- objects. In the case of Circle/Ellipse, the setSize(x,y) member function
- violates this substitutability.
-
- You have three choices: [1] remove the setSize(x,y) member function from
- Ellipse (thus breaking existing code that calls the setSize(x,y) member
- function), [2] allow a Circle to have a different height than width (an
- asymmetrical circle; hmmm), or [3] drop the inheritance relationship. Sorry,
- but there simply are no other choices. Note that some people mention the
- option of deriving both Circle and Ellipse from a third common base class, but
- that's just a variant of option [3] above.
-
- Another way to say this is that you have to either make the base class weaker
- (in this case braindamage Ellipse to the point that you can't set its width and
- height to different values), or make the derived class stronger (in this case
- empower a Circle with the ability to be both symmetric and, ahem, asymmetric).
- When neither of these is very satisfying (such as in the Circle/Ellipse case),
- one normally simply removes the inheritance relationship. If the inheritance
- relationship simply has to exist, you may need to remove the mutator member
- functions (setHeight(y), setWidth(x), and setSize(x,y)) from the base class.
-
- (Note: this FAQ has to do with public inheritance; private and protected
- inheritance[24] are different.)
-
- (Note: setSize(x,y) isn't sacred. Depending on your goals, it may be okay to
- prevent users from changing the dimensions of an Ellipse, in which case it
- would be a valid design choice to not have a setSize(x,y) method in Ellipse.
- However this series of FAQs discusses what to do when you want to create a
- derived class of a pre-existing base class that has an "unacceptable" method in
- it. Of course the ideal situation is to discover this problem when the base
- class doesn't yet exist. But life isn't always ideal...)
-
- ==============================================================================
-
- [21.9] But my problem doesn't have anything to do with circles and ellipses, so
- what good is that silly example to me?
-
- Ahhh, there's the rub. You think the Circle/Ellipse example is just a silly
- example. But in reality, your problem is an isomorphism to that example.
-
- I don't care what your inheritance problem is, but all (yes all) bad
- inheritances boil down to the Circle-is-not-a-kind-of-Ellipse example.
-
- Here's why: Bad inheritances always have a base class with an extra capability
- (often an extra member function or two; sometimes an extra promise made by one
- or a combination of member functions) that a derived class can't satisfy.
- You've either got to make the base class weaker, make the derived class
- stronger, or eliminate the proposed inheritance relationship. I've seen lots
- and lots and lots of these bad inheritance proposals, and believe me, they all
- boil down to the Circle/Ellipse example.
-
- Therefore, if you truly understand the Circle/Ellipse example, you'll be able
- to recognize bad inheritance everywhere. If you don't understand what's going
- on with the Circle/Ellipse problem, the chances are high that you'll make some
- very serious and very expensive inheritance mistakes.
-
- Sad but true.
-
- (Note: this FAQ has to do with public inheritance; private and protected
- inheritance[24] are different.)
-
- ==============================================================================
-
- SECTION [22]: Inheritance -- abstract base classes (ABCs)
-
-
- [22.1] What's the big deal of separating interface from implementation?
-
- Interfaces are a company's most valuable resources. Designing an interface
- takes longer than whipping together a concrete class which fulfills that
- interface. Furthermore interfaces require the time of more expensive people.
-
- Since interfaces are so valuable, they should be protected from being tarnished
- by data structures and other implementation artifacts. Thus you should
- separate interface from implementation.
-
- ==============================================================================
-
- [22.2] How do I separate interface from implementation in C++ (like Modula-2)?
-
- Use an ABC[22.3].
-
- ==============================================================================
-
- [22.3] What is an ABC?
-
- An abstract base class.
-
- At the design level, an abstract base class (ABC) corresponds to an abstract
- concept. If you asked a mechanic if he repaired vehicles, he'd probably wonder
- what kind-of vehicle you had in mind. Chances are he doesn't repair space
- shuttles, ocean liners, bicycles, or nuclear submarines. The problem is that
- the term "vehicle" is an abstract concept (e.g., you can't build a "vehicle"
- unless you know what kind of vehicle to build). In C++, class Vehicle would be
- an ABC, with Bicycle, SpaceShuttle, etc, being subclasses (an OceanLiner
- is-a-kind-of-a Vehicle). In real-world OO, ABCs show up all over the place.
-
- At the programming language level, an ABC is a class that has one or more pure
- virtual[22.4] member functions. You cannot make an object (instance) of an
- ABC.
-
- ==============================================================================
-
- [22.4] What is a "pure virtual" member function?
-
- A member function declaration that turns a normal class into an abstract class
- (i.e., an ABC). You normally only implement it in a derived class.
-
- Some member functions exist in concept; they don't have any reasonable
- definition. E.g., suppose I asked you to draw a Shape at location (x,y) that
- has size 7. You'd ask me "what kind of shape should I draw?" (circles,
- squares, hexagons, etc, are drawn differently). In C++, we must indicate the
- existence of the draw() member function (so users can call it when they have a
- Shape* or a Shape&), but we recognize it can (logically) be defined only in
- subclasses:
-
- class Shape {
- public:
- virtual void draw() const = 0; // = 0 means it is "pure virtual"
- // ...
- };
-
- This pure virtual function makes Shape an ABC. If you want, you can think of
- the "= 0;" syntax as if the code were at the NULL pointer. Thus Shape promises
- a service to its users, yet Shape isn't able to provide any code to fulfill
- that promise. This forces any actual object created from a [concrete] class
- derived from Shape to have the indicated member function, even though the base
- class doesn't have enough information to actually define it yet.
-
- Note that it is possible to provide a definition for a pure virtual function,
- but this usually confuses novices and is best avoided until later.
-
- ==============================================================================
-
- [22.5] How do you define a copy constructor or assignment operator for a class
- that contains a pointer to a (abstract) base class?
-
- If the class "owns" the object pointed to by the (abstract) base class pointer,
- use the Virtual Constructor Idiom[20.5] in the (abstract) base class. As usual
- with this idiom, we declare a pure virtual[22.4] clone() method in the base
- class:
-
- class Shape {
- public:
- // ...
- virtual Shape* clone() const = 0; // The Virtual (Copy) Constructor[20.5]
- // ...
- };
-
- Then we implement this clone() method in each derived class:
-
- class Circle {
- public:
- // ...
- virtual Shape* clone() const { return new Circle(*this); }
- // ...
- };
-
- class Square {
- public:
- // ...
- virtual Shape* clone() const { return new Square(*this); }
- // ...
- };
-
- Now suppose that each Fred object "has-a" Shape object. Naturally the Fred
- object doesn't know whether the Shape is Circle or a Square or ... Fred's copy
- constructor and assignment operator will invoke Shape's clone() method to copy
- the object:
-
- class Fred {
- public:
- Fred(Shape* p) : p_(p) { assert(p != NULL); } // p must not be NULL
- ~Fred() { delete p; }
- Fred(const Fred& f) : p_(f.p_->clone()) { }
- Fred& operator= (const Fred& f)
- {
- if (this != &f) { // Check for self-assignment
- Shape* p2 = f.p_->clone(); // Create the new one FIRST...
- delete p_; // ...THEN delete the old one
- p_ = p2;
- }
- return *this;
- }
- // ...
- private:
- Shape* p_;
- };
-
- ==============================================================================
-
- SECTION [23]: Inheritance -- what your mother never told you
-
-
- [23.1] When my base class's constructor calls a virtual function, why doesn't
- my derived class's override of that virtual function get invoked?
- [UPDATED!]
-
- [Recently rewrote (on 1/97).]
-
- During the class Base's constructor, the object isn't yet a Derived, so if
- Base::Base() calls a virtual[20] function virt(), the Base::virt() will be
- invoked, even if Derived::virt() exists.
-
- Similarly, during Base's destructor, the object is no longer a Derived, so when
- Base::~Base() calls virt(), Base::virt() gets control, not the Derived::virt()
- override.
-
- You'll quickly see the wisdom of this approach when you imagine the disaster if
- Derived::virt() touched a member object from class Derived. In particular, if
- Base::Base() called the virtual function virt(), this rule causes Base::virt()
- to be invoked. If it weren't for this rule, Derived::virt() would get called
- before the Derived part of a Derived object is constructed, and Derived::virt()
- could touch unconstructed member objects from the Derived part of a Derived
- object. That would be a disaster.
-
- ==============================================================================
-
- [23.2] Should a derived class replace ("override") a non-virtual function from
- a base class?
-
- It's legal, but it ain't moral.
-
- Experienced C++ programmers will sometimes redefine a non-virtual function
- (e.g., the derived class implementation might make better use of the derived
- class's resources for efficiency), or to get around the hiding rule[23.3].
- However the client-visible effects must be identical, since non-virtual
- functions are dispatched based on the static type of the pointer/reference
- rather than the dynamic type of the pointed-to/referenced object.
-
- ==============================================================================
-
- [23.3] What's the meaning of, Warning: Derived::f(int) hides Base::f(float)?
-
- It means you're going to die.
-
- Here's the mess you're in: if Derived declares a member function named f(), and
- Base declares a member function named f() with a different signature (e.g.,
- different parameter types and/or constness), then the Base f() is "hidden"
- rather than "overloaded" or "overridden" (even if the Base f() is virtual[20]).
-
- Here's how you get out of the mess: Derived must redefine the Base member
- function(s) that are hidden (even if they are non-virtual). Normally this
- re-definition merely calls the appropriate Base member function. E.g.,
-
- class Base {
- public:
- void f(int);
- };
-
- class Derived : public Base {
- public:
- void f(double);
- void f(int i) { Base::f(i); } // The redefinition merely calls Base::f(int)
- };
-
- ==============================================================================
-
- [23.4] What does it mean that the "virtual table" is an unresolved external?
-
- If you get a link error of the form
- "Error: Unresolved or undefined symbols detected: virtual table for class Fred,"
- you probably have an undefined virtual[20] member function in class Fred.
-
- The compiler typically creates a magical data structure called the "virtual
- table" for classes that have virtual functions (this is how it handles dynamic
- binding[20.2]). Normally you don't have to know about it at all. But if you
- forget to define a virtual function for class Fred, you will sometimes get this
- linker error.
-
- Here's the nitty gritty: Many compilers put this magical "virtual table" in the
- compilation unit that defines the first non-inline virtual function in the
- class. Thus if the first non-inline virtual function in Fred is wilma(), the
- compiler will put Fred's virtual table in the same compilation unit where it
- sees Fred::wilma(). Unfortunately if you accidentally forget to define
- Fred::wilma(), rather than getting a Fred::wilma() is undefined, you may get a
- "Fred's virtual table is undefined". Sad but true.
-
- ==============================================================================
-
-